package com.me.test.threadpool;

import java.io.*;
import java.net.*;
import java.util.*;

// uses class ObjectFIFO from chapter 18

public class HttpWorker extends Object {
	private static int nextWorkerID = 0;

	private File docRoot;
	private ObjectFIFO idleWorkers;
	private int workerID;
	private ObjectFIFO handoffBox;

	private Thread internalThread;
	private volatile boolean noStopRequested;

	public HttpWorker(File docRoot, int workerPriority, ObjectFIFO idleWorkers) {

		this.docRoot = docRoot;
		this.idleWorkers = idleWorkers;

		workerID = getNextWorkerID();
		handoffBox = new ObjectFIFO(1); // only one slot

		// Just before returning, the thread should be
		// created and started.
		noStopRequested = true;

		Runnable r = new Runnable() {
			public void run() {
				try {
					runWork();
				} catch (Exception x) {
					// in case ANY exception slips through
					x.printStackTrace();
				}
			}
		};

		internalThread = new Thread(r);
		internalThread.setPriority(workerPriority);
		internalThread.setName("HttpWorker-" + workerID);
		internalThread.start();
	}

	public static synchronized int getNextWorkerID() {
		// synchronized at the class level to ensure uniqueness
		int id = nextWorkerID;
		nextWorkerID++;
		return id;
	}

	public void processRequest(Socket s) throws InterruptedException {
		handoffBox.add(s);
	}

	private void runWork() {
		Socket s = null;
		InputStream in = null;
		OutputStream out = null;

		while (noStopRequested) {
			try {
				// Worker is ready to receive new service
				// requests, so it adds itself to the idle
				// worker queue.
				idleWorkers.add(this);

				// Wait here until the server puts a request
				// into the handoff box.
				s = (Socket) handoffBox.remove();

				in = s.getInputStream();
				out = s.getOutputStream();
				generateResponse(in, out);
				out.flush();
			} catch (IOException iox) {
				System.err.println("I/O error while processing request, "
						+ "ignoring and adding back to idle "
						+ "queue - workerID=" + workerID);
			} catch (InterruptedException x) {
				// re-assert the interrupt
				Thread.currentThread().interrupt();
			} finally {
				// Try to close everything, ignoring
				// any IOExceptions that might occur.
				if (in != null) {
					try {
						in.close();
					} catch (IOException iox) {
						// ignore
					} finally {
						in = null;
					}
				}

				if (out != null) {
					try {
						out.close();
					} catch (IOException iox) {
						// ignore
					} finally {
						out = null;
					}
				}

				if (s != null) {
					try {
						s.close();
					} catch (IOException iox) {
						// ignore
					} finally {
						s = null;
					}
				}
			}
		}
	}

	private void generateResponse(InputStream in, OutputStream out)
			throws IOException {

		BufferedReader reader = new BufferedReader(new InputStreamReader(in));

		String requestLine = reader.readLine();

		if ((requestLine == null) || (requestLine.length() < 1)) {

			throw new IOException("could not read request");
		}

		System.out.println("workerID=" + workerID + ", requestLine="
				+ requestLine);

		StringTokenizer st = new StringTokenizer(requestLine);
		String filename = null;

		try {
			// request method, typically 'GET', but ignored
			st.nextToken();

			// the second token should be the filename
			filename = st.nextToken();
		} catch (NoSuchElementException x) {
			throw new IOException("could not parse request line");
		}

		File requestedFile = generateFile(filename);

		BufferedOutputStream buffOut = new BufferedOutputStream(out);

		if (requestedFile.exists()) {
			System.out
					.println("workerID=" + workerID + ", 200 OK: " + filename);

			int fileLen = (int) requestedFile.length();

			BufferedInputStream fileIn = new BufferedInputStream(
					new FileInputStream(requestedFile));

			// Use this utility to make a guess about the
			// content type based on the first few bytes
			// in the stream.
			String contentType = URLConnection
					.guessContentTypeFromStream(fileIn);

			byte[] headerBytes = createHeaderBytes("HTTP/1.0 200 OK", fileLen,
					contentType);

			buffOut.write(headerBytes);

			byte[] buf = new byte[2048];
			int blockLen = 0;

			while ((blockLen = fileIn.read(buf)) != -1) {
				buffOut.write(buf, 0, blockLen);
			}

			fileIn.close();
		} else {
			System.out.println("workerID=" + workerID + ", 404 Not Found: "
					+ filename);

			byte[] headerBytes = createHeaderBytes("HTTP/1.0 404 Not Found",
					-1, null);

			buffOut.write(headerBytes);
		}

		buffOut.flush();
	}

	private File generateFile(String filename) {
		File requestedFile = docRoot; // start at the base

		// Build up the path to the requested file in a
		// platform independent way. URL's use '/' in their
		// path, but this platform may not.
		StringTokenizer st = new StringTokenizer(filename, "/");
		while (st.hasMoreTokens()) {
			String tok = st.nextToken();

			if (tok.equals("..")) {
				// Silently ignore parts of path that might
				// lead out of the document root area.
				continue;
			}

			requestedFile = new File(requestedFile, tok);
		}

		if (requestedFile.exists() && requestedFile.isDirectory()) {

			// If a directory was requested, modify the request
			// to look for the "index.html" file in that
			// directory.
			requestedFile = new File(requestedFile, "index.html");
		}

		return requestedFile;
	}

	private byte[] createHeaderBytes(String resp, int contentLen,
			String contentType) throws IOException {

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos));

		// Write the first line of the response, followed by
		// the RFC-specified line termination sequence.
		writer.write(resp + "\r\n");

		// If a length was specified, add it to the header
		if (contentLen != -1) {
			writer.write("Content-Length: " + contentLen + "\r\n");
		}

		// If a type was specified, add it to the header
		if (contentType != null) {
			writer.write("Content-Type: " + contentType + "\r\n");
		}

		// A blank line is required after the header.
		writer.write("\r\n");
		writer.flush();

		byte[] data = baos.toByteArray();
		writer.close();

		return data;
	}

	public void stopRequest() {
		noStopRequested = false;
		internalThread.interrupt();
	}

	public boolean isAlive() {
		return internalThread.isAlive();
	}
}
